/*- * See the file LICENSE for redistribution information. * * Copyright (c) 2002-2006 * Sleepycat Software. All rights reserved. * * $Id: UtilizationTracker.java,v 1.1 2006/05/06 09:01:58 ckaestne Exp $ */ package com.sleepycat.je.cleaner; import java.util.ArrayList; import java.util.List; import com.sleepycat.je.DatabaseException; import com.sleepycat.je.dbi.EnvironmentImpl; import com.sleepycat.je.dbi.MemoryBudget; import com.sleepycat.je.log.LogEntryType; import com.sleepycat.je.utilint.DbLsn; /** * Tracks changes to the utilization profile since the last checkpoint. * * <p>All changes to this object occur must under the log write latch. It is * possible to read tracked info without holding the latch. This is done by * the cleaner when selecting a file and by the checkpointer when determining * what FileSummaryLNs need to be written. To read tracked info outside the * log write latch, call getTrackedFile or getTrackedFiles. activateCleaner * can also be called outside the latch.</p> */ public class UtilizationTracker { private EnvironmentImpl env; private Cleaner cleaner; private List files; private long activeFile; private TrackedFileSummary[] snapshot; private long bytesSinceActivate; /** * Creates an empty tracker. The cleaner field of the environment object * must be initialized before using this constructor. */ public UtilizationTracker(EnvironmentImpl env) throws DatabaseException { this(env, env.getCleaner()); } /** * Constructor used by the cleaner constructor, prior to setting the * cleaner field of the environment. */ UtilizationTracker(EnvironmentImpl env, Cleaner cleaner) throws DatabaseException { assert cleaner != null; this.env = env; this.cleaner = cleaner; files = new ArrayList(); snapshot = new TrackedFileSummary[0]; activeFile = -1; } public EnvironmentImpl getEnvironment() { return env; } /** * Evicts tracked detail if the budget for the tracker is exceeded. Evicts * only one file summary LN at most to keep eviction batches small. * Returns the number of bytes freed. * * <p>When flushFileSummary is called, the TrackedFileSummary is cleared via * its reset method, which is called by FileSummaryLN.writeToLog. This is * how memory is subtracted from the budget.</p> */ public long evictMemory() throws DatabaseException { /* If not tracking detail, there is nothing to evict. */ if (!cleaner.trackDetail) { return 0; } /* * Do not start eviction until after recovery, since the * UtilizationProfile will not be initialized properly. UP * initialization requires that all LNs have been replayed. */ if (!env.isOpen()) { return 0; } MemoryBudget mb = env.getMemoryBudget(); long totalEvicted = 0; long totalBytes = 0; int largestBytes = 0; TrackedFileSummary bestFile = null; /* * Use a local variable to access the array since the snapshot * field can be changed by other threads. */ TrackedFileSummary[] a = snapshot; for (int i = 0; i < a.length; i += 1) { TrackedFileSummary tfs = a[i]; int mem = tfs.getMemorySize(); totalBytes += mem; if (mem > largestBytes && tfs.getAllowFlush()) { largestBytes = mem; bestFile = tfs; } } if (bestFile != null && totalBytes > mb.getTrackerBudget()) { env.getUtilizationProfile().flushFileSummary(bestFile); totalEvicted += largestBytes; } return totalEvicted; } /** * Wakeup the cleaner thread and reset the log byte counter. */ public void activateCleaner() { env.getCleaner().wakeup(); bytesSinceActivate = 0; } /** * Returns a snapshot of the files being tracked as of the last time a * log entry was added. The summary info returned is the delta since the * last checkpoint, not the grand totals, and is approximate since it is * changing in real time. This method may be called without holding the * log write latch. * * <p>If files are added or removed from the list of tracked files in real * time, the returned array will not be changed since it is a snapshot. * But the objects contained in the array are live and will be updated in * real time under the log write latch. The array and the objects in the * array should not be modified by the caller.</p> */ public TrackedFileSummary[] getTrackedFiles() { return snapshot; } /** * Returns one file from the snapshot of tracked files, or null if the * given file number is not in the snapshot array. * @see #getTrackedFiles */ public TrackedFileSummary getTrackedFile(long fileNum) { /* * Use a local variable to access the array since the snapshot field * can be changed by other threads. */ TrackedFileSummary[] a = snapshot; for (int i = 0; i < a.length; i += 1) { if (a[i].getFileNumber() == fileNum) { return a[i]; } } return null; } /** * Counts the addition of all new log entries including LNs, and returns * whether the cleaner should be woken. * * <p>Must be called under the log write latch.</p> */ public boolean countNewLogEntry(long lsn, LogEntryType type, int size) { TrackedFileSummary file = getFile(DbLsn.getFileNumber(lsn)); file.totalCount += 1; file.totalSize += size; if (type.isNodeType()) { if (inArray(type, LogEntryType.IN_TYPES)) { file.totalINCount += 1; file.totalINSize += size; } else { file.totalLNCount += 1; file.totalLNSize += size; } } bytesSinceActivate += size; return (bytesSinceActivate >= env.getCleaner().cleanerBytesInterval); } /** * Counts a node that has become obsolete and tracks the LSN offset to * avoid a lookup during cleaning. * * <p>This method should only be called for LNs and INs (i.e, only for * nodes). If type is null we assume it is an LN.</p> * * <p>Must be called under the log write latch.</p> */ public void countObsoleteNode(long lsn, LogEntryType type) { TrackedFileSummary file = getFile(DbLsn.getFileNumber(lsn)); countOneNode(file, type); file.trackObsolete(DbLsn.getFileOffset(lsn)); } /** * Counts as countObsoleteNode does, but since the LSN may be inexact, does * not track the obsolete LSN offset. * * <p>This method should only be called for LNs and INs (i.e, only for * nodes). If type is null we assume it is an LN.</p> * * <p>Must be called under the log write latch.</p> */ public void countObsoleteNodeInexact(long lsn, LogEntryType type) { TrackedFileSummary file = getFile(DbLsn.getFileNumber(lsn)); countOneNode(file, type); } /** * Counts a change in the obsolete status of an node, incrementing the * obsolete count if obsolete is true and decrementing it if obsolete is * false. */ private void countOneNode(TrackedFileSummary file, LogEntryType type) { if (type == null || type.isNodeType()) { if (type == null || !inArray(type, LogEntryType.IN_TYPES)) { file.obsoleteLNCount += 1; } else { file.obsoleteINCount += 1; } } } /** * Adds changes from a given TrackedFileSummary. * * <p>Must be called under the log write latch.</p> */ public void addSummary(long fileNumber, TrackedFileSummary other) { TrackedFileSummary file = getFile(fileNumber); file.addTrackedSummary(other); } /** * Returns a tracked summary for the given file which will not be flushed. * Used for watching changes that occur while a file is being cleaned. */ public TrackedFileSummary getUnflushableTrackedSummary(long fileNum) throws DatabaseException { TrackedFileSummary file = getFile(fileNum); file.setAllowFlush(false); return file; } /** * Returns a tracked file for the given file number, adding an empty one * if the file is not already being tracked. * * <p>Must be called under the log write latch.</p> */ private TrackedFileSummary getFile(long fileNum) { if (activeFile < fileNum) { activeFile = fileNum; } int size = files.size(); for (int i = 0; i < size; i += 1) { TrackedFileSummary file = (TrackedFileSummary) files.get(i); if (file.getFileNumber() == fileNum) { return file; } } /* * Create a new tracking object and take a snapshot of the updated file * list. */ TrackedFileSummary file = new TrackedFileSummary (this, fileNum, cleaner.trackDetail); files.add(file); takeSnapshot(); return file; } /** * Called after the FileSummaryLN is written to the log during checkpoint. * * <p>We keep the active file summary in the tracked file list, but we * remove older files to prevent unbounded growth of the list.</p> * * <p>Must be called under the log write latch.</p> */ void resetFile(TrackedFileSummary file) { if (file.getFileNumber() < activeFile && file.getAllowFlush()) { files.remove(file); takeSnapshot(); } } /** * Takes a snapshot of the tracked file list. * * <p>Must be called under the log write latch.</p> */ private void takeSnapshot() { /* * Only assign to the snapshot field with a populated array, since it * will be accessed by other threads. */ TrackedFileSummary[] a = new TrackedFileSummary[files.size()]; files.toArray(a); snapshot = a; } /** * Returns whether an object reference is in an array. */ private boolean inArray(Object o, Object[] a) { for (int i = 0; i < a.length; i += 1) { if (a[i] == o) { return true; } } return false; } }